﻿#include "RenderEngine.h"

#include <cassert>
#include <iostream>

#include "glm/gtc/matrix_transform.hpp"
#include "stb_image/stb_image.cpp"
#include <OpenEXR/ImfRgbaFile.h>
#include <OpenEXR/ImfArray.h>
#include <Imath/ImathBox.h>

#include "Camera.h"
#include "setting.h"
#include "Shader.h"

RenderEngine *RenderEngine::instance = nullptr;

// clang-format off
constexpr float vertices[] = {
        // positions          // normals           // texture coords
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  0.0f,
         0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  0.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  1.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f,  1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f,  0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  0.0f,
         0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f,  1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f,  0.0f,

        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  1.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f,  0.0f,

         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,
         0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  1.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  1.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  1.0f,
         0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  0.0f,
         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f,  1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  1.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f,  1.0f
    };
glm::vec3 cubePositions[] = {
    glm::vec3( 0.0f,  0.0f,  0.0f),
    glm::vec3( 2.0f,  5.0f, -15.0f),
    glm::vec3(-1.5f, -2.2f, -2.5f),
    glm::vec3(-3.8f, -2.0f, -12.3f),
    glm::vec3( 2.4f, -0.4f, -3.5f),
    glm::vec3(-1.7f,  3.0f, -7.5f),
    glm::vec3( 1.3f, -2.0f, -2.5f),
    glm::vec3( 1.5f,  2.0f, -2.5f),
    glm::vec3( 1.5f,  0.2f, -1.5f),
    glm::vec3(-1.3f,  1.0f, -1.5f)
};
glm::vec3 pointLightPositions[] = {
    glm::vec3( 0.7f,  0.2f,  2.0f),
    glm::vec3( 2.3f, -3.3f, -4.0f),
    glm::vec3(-4.0f,  2.0f, -12.0f),
    glm::vec3( 0.0f,  0.0f, -3.0f)
};
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

float planeVertices[] = {
	// positions          // texture Coords
	5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
   -5.0f, -0.5f,  5.0f,  0.0f, 0.0f,
   -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,

	5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
   -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
	5.0f, -0.5f, -5.0f,  2.0f, 2.0f
};

float framebufferQuadVertices[] = {
	// positions   // texCoords
	-1.0f,  1.0f,  0.0f, 1.0f,
	-1.0f, -1.0f,  0.0f, 0.0f,
	 1.0f, -1.0f,  1.0f, 0.0f,

	-1.0f,  1.0f,  0.0f, 1.0f,
	 1.0f, -1.0f,  1.0f, 0.0f,
	 1.0f,  1.0f,  1.0f, 1.0f
};
// clang-format on

RenderEngine::RenderEngine()
{
	assert(instance == nullptr);
	instance = this;

	// glfw: initialize and configure
	// ------------------------------
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

	// glfw window creation
	// --------------------
	window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr);
	if (window == nullptr)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return;
	}
	glfwMakeContextCurrent(window);
	glfwSetWindowUserPointer(window, this);
	glfwSetFramebufferSizeCallback(window, [](GLFWwindow *window, const int width, const int height)
	{
		// make sure the viewport matches the new window dimensions; note that width and
		// height will be significantly larger than specified on retina displays.
		glViewport(0, 0, width, height);
	});
	glfwSetWindowCloseCallback(window, [](GLFWwindow *window)
	{
		if (auto *engine = static_cast<RenderEngine *>(glfwGetWindowUserPointer(window)))
		{
			engine->shouldClose = true;
		}
	});

	glfwSetMouseButtonCallback(window, [](GLFWwindow *window, const int button, const int action, const int mods)
	{
		if (auto *engine = static_cast<RenderEngine *>(glfwGetWindowUserPointer(window)))
		{
			if (button == GLFW_MOUSE_BUTTON_RIGHT)
			{
				if (action == GLFW_PRESS)
				{
					engine->mouseRightPressed = true;
				}
				else
				{
					engine->mouseRightPressed = false;
					engine->firstMouse = true;
				}
			}

			if (button == GLFW_KEY_LEFT_SHIFT && action == GLFW_RELEASE) {}
		}
	});

	glfwSetCursorPosCallback(window, [](GLFWwindow *window, const double xPos, const double yPos)
	{
		if (auto *engine = static_cast<RenderEngine *>(glfwGetWindowUserPointer(window)))
		{
			if (!engine->mouseRightPressed)
			{
				return;
			}

			if (engine->firstMouse)
			{
				engine->lastX = xPos;
				engine->lastY = yPos;
				engine->firstMouse = false;
			}

			const double xOffset = xPos - engine->lastX;
			const double yOffset = engine->lastY - yPos; // reversed since y-coordinates go from bottom to top

			engine->lastX = xPos;
			engine->lastY = yPos;
			engine->camera->ProcessMouseMovement(xOffset, yOffset);
		}
	});
	glfwSetKeyCallback(window, [](GLFWwindow *window, int key, int scancode, int action, int mods) {});

	// VSync enable
	glfwSwapInterval(1);

	// glad: load all OpenGL function pointers
	// ---------------------------------------
	if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress)))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return;
	}

	// 深度测试
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);

	// 面剔除
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
}

RenderEngine::~RenderEngine()
{
	glfwDestroyWindow(window);
	std::cout << "退出渲染引擎" << std::endl;
}

void RenderEngine::Run()
{
	camera = std::make_unique<Camera>(glm::vec3(0.0f, 0.0f, 0.5f));

	litShader = std::make_unique<Shader>("resources/shader/Lit.vert", "resources/shader/Lit.frag");
	lightShader = std::make_unique<Shader>("resources/shader/light.vert", "resources/shader/light.frag");

	diffuseMap = LoadTexture("resources/model/knife/MagnumSurvivalKnife_MTL_BaseColor.png");
	LoadCubeMap("resources/textures/rogland_clear_night_4k.exr");
	// specularMap = LoadTexture("resources/textures/container_specular.png");
	// floorTexture = LoadTexture("resources/textures/floor.png");

	litMaterial = std::make_unique<Material>(*litShader);
	lightMaterial = std::make_unique<Material>(*lightShader);

	model = std::make_unique<Model>("resources/model/knife/Magnum Survival Knife.fbx", *litMaterial);

	unsigned int VBO;
	glGenVertexArrays(1, &modelVAO);
	glGenBuffers(1, &VBO);

	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBindVertexArray(modelVAO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), static_cast<void *>(nullptr));
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), reinterpret_cast<void *>(3 * sizeof(float)));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), reinterpret_cast<void *>(6 * sizeof(float)));
	glEnableVertexAttribArray(2);

	glGenVertexArrays(1, &lightVAO);
	glBindVertexArray(lightVAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), static_cast<void *>(nullptr));
	glEnableVertexAttribArray(0);

	unsigned int planeVBO;
	glGenVertexArrays(1, &planeVAO);
	glGenBuffers(1, &planeVBO);
	glBindVertexArray(planeVAO);
	glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), static_cast<void *>(nullptr));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), reinterpret_cast<void *>(3 * sizeof(float)));

	unsigned int quadVBO;
	glGenVertexArrays(1, &screenQuadVAO);
	glGenBuffers(1, &quadVBO);
	glBindVertexArray(screenQuadVAO);
	glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(framebufferQuadVertices), &framebufferQuadVertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), static_cast<void *>(nullptr));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), reinterpret_cast<void *>(2 * sizeof(float)));

	litShader->use();
	litMaterial->setInt("material.texture_diffuse1", 0);
	litMaterial->setInt("material.texture_specular1", 1);

	InitFramebuffer();

	RenderLoop();

	glDeleteVertexArrays(1, &modelVAO);
	glDeleteVertexArrays(1, &lightVAO);
	glDeleteBuffers(1, &VBO);

	glfwTerminate();
}

void RenderEngine::InitFramebuffer()
{
	glGenFramebuffers(1, &framebuffer);
	glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

	glGenTextures(1, &colorbufferTexture);
	glBindTexture(GL_TEXTURE_2D, colorbufferTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // 将坐标的范围限制在(0, 0)到(1, 1)内重复
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, 0);

	// 绑定纹理, 以便将帧缓冲区数据绘制到纹理上
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbufferTexture, 0);

	// 为渲染缓冲对象分配内存
	unsigned int renderBuffer;
	glGenRenderbuffers(1, &renderBuffer);
	glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);

	// 将渲染缓冲对象附加到帧缓冲的深度和模板附件上
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer);

	// 检查帧缓冲是否完整
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		std::cout << "Framebuffer is not complete!" << std::endl;

	// 解绑帧缓冲，保证不会渲染到错误的帧缓冲上。
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	framebufferShader = std::make_unique<Shader>("resources/shader/framebuffer.vert",
	                                             "resources/shader/framebuffer.frag");
}

void RenderEngine::RenderLoop()
{
	while (!shouldClose)
	{
		CalculateDeltaTimeAndFPS();
		ProcessInput();

		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

		glEnable(GL_DEPTH_TEST);
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

		Rendering();

		glBindFramebuffer(GL_FRAMEBUFFER, 0);

		glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		framebufferShader->use();
		glBindVertexArray(screenQuadVAO);
		glDisable(GL_DEPTH_TEST);
		glBindTexture(GL_TEXTURE_2D, colorbufferTexture);
		glDrawArrays(GL_TRIANGLES, 0, 6);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
}

void RenderEngine::ProcessInput() const
{
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		camera->ProcessKeyboard(FORWARD, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		camera->ProcessKeyboard(BACKWARD, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		camera->ProcessKeyboard(LEFT, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		camera->ProcessKeyboard(RIGHT, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
		camera->ProcessKeyboard(UP, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
		camera->ProcessKeyboard(DOWN, deltaTime);
}

void RenderEngine::CalculateDeltaTimeAndFPS()
{
	const double currentTime = glfwGetTime();
	deltaTime = currentTime - lastFrameTime;
	lastFrameTime = currentTime;
	frameCount = static_cast<int>(1.0f / deltaTime);

	static double lastUpdateTime = 0.0;
	if (currentTime - lastUpdateTime >= 0.5)
	{
		std::string title = "OpenGL Rendering - ";
		title += "frame: ";
		title += std::to_string(frameCount);
		title += " | ";
		title += "Delta Time: ";
		title += std::to_string(deltaTime * 1000);
		title += " ms";

		glfwSetWindowTitle(window, title.c_str());

		lastUpdateTime = currentTime; // 更新最后更新时间
	}
}

void RenderEngine::Rendering() const
{
	// be sure to activate shader when setting uniforms/drawing objects
	litShader->use();
	litMaterial->setVec3("light.position", lightPos);
	litMaterial->setVec3("viewPos", camera->Position);

	// light properties
	litMaterial->setVec3("light.ambient", glm::vec3(0.2f, 0.2f, 0.2f));
	litMaterial->setVec3("light.diffuse", glm::vec3(0.5f, 0.5f, 0.5f));
	litMaterial->setVec3("light.specular", glm::vec3(1.0f, 1.0f, 1.0f));

	// material properties
	litMaterial->setFloat("material.shininess", 64.0f);

	const glm::mat4 projectionMatrix = glm::perspective(glm::radians(camera->Zoom),
	                                                    static_cast<float>(SCR_WIDTH) / static_cast<float>(SCR_HEIGHT),
	                                                    0.1f,
	                                                    100.0f);
	const glm::mat4 viewMatrix = camera->GetViewMatrix();
	auto modelMatrix = glm::mat4(1.0f);

	litShader->use();
	litMaterial->setMat4("projection", projectionMatrix);
	litMaterial->setMat4("view", viewMatrix);
	litMaterial->setMat4("model", modelMatrix);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, diffuseMap);
	// glActiveTexture(GL_TEXTURE1);
	// glBindTexture(GL_TEXTURE_2D, specularMap);
	glBindVertexArray(modelVAO);
	// glDrawArrays(GL_TRIANGLES, 0, 36);
	model->Draw();
	glBindVertexArray(0);

	// floorShader->use();
	// floorMaterial->setMat4("projection", projectionMatrix);
	// floorMaterial->setMat4("view", viewMatrix);
	// floorMaterial->setMat4("model", modelMatrix);
	// glActiveTexture(GL_TEXTURE0);
	// glBindTexture(GL_TEXTURE_2D, diffuseMap);
	glBindVertexArray(planeVAO);
	lightMaterial->setMat4("model", glm::mat4(1.0f));
	glDrawArrays(GL_TRIANGLES, 0, 6);
	glBindVertexArray(0);

	lightShader->use();
	lightMaterial->setMat4("projection", projectionMatrix);
	lightMaterial->setMat4("view", viewMatrix);
	modelMatrix = glm::mat4(1.0f);
	modelMatrix = glm::translate(modelMatrix, lightPos);
	modelMatrix = glm::scale(modelMatrix, glm::vec3(0.2f));
	lightMaterial->setMat4("model", modelMatrix);
	glBindVertexArray(lightVAO);
	glDrawArrays(GL_TRIANGLES, 0, 36);
	glBindVertexArray(0);
}

// ReSharper disable once CppDFAConstantParameter
unsigned int RenderEngine::LoadTexture(char const *path)
{
	unsigned int textureID;
	glGenTextures(1, &textureID);

	int width, height, nrComponents;
	if (unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0))
	{
		GLenum format;
		if (nrComponents == 1)
			format = GL_RED;
		else if (nrComponents == 3)
			format = GL_RGB;
		else
			format = GL_RGBA;

		glBindTexture(GL_TEXTURE_2D, textureID);
		glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLint>(format), width, height, 0, format, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		stbi_image_free(data);
	}
	else
	{
		std::cout << "Texture failed to load at path: " << path << std::endl;
		stbi_image_free(data);
	}

	return textureID;
}

// ReSharper disable once CppDFAConstantParameter
void RenderEngine::LoadCubeMap(char const *path)
{
	// unsigned int textureID;
	// glGenTextures(1, &textureID);
	// glBindTexture(GL_TEXTURE_2D, textureID);

	try
	{
		// // 未优化
		// Imf::RgbaInputFile in(path);
		// const Imath::Box2i win = in.dataWindow();
		// const Imath::V2i dim(win.max.x - win.min.x + 1, win.max.y - win.min.y + 1);
		// const int width = dim.x;
		// const int height = dim.y;
		//
		// auto *pixelBuffer = new Imf::Rgba[width * height];
		// const int dx = win.min.x;
		// const int dy = win.min.y;
		// in.setFrameBuffer(pixelBuffer - dx - dy * width, 1, width);
		// in.readPixels(win.min.y, win.max.y);
		//
		// // 转换为 float 数组 (RGBA)
		// std::vector<float> floatPixels(width * height * 4);
		// Imf::Rgba &rgba = pixelBuffer[0];
		// for (int y = 0; y < height; ++y)
		// {
		// 	for (int x = 0; x < width; ++x)
		// 	{
		// 		rgba = pixelBuffer[y * width + x];
		// 		floatPixels[(y * width + x) * 4 + 0] = rgba.r;
		// 		floatPixels[(y * width + x) * 4 + 1] = rgba.g;
		// 		floatPixels[(y * width + x) * 4 + 2] = rgba.b;
		// 		floatPixels[(y * width + x) * 4 + 3] = rgba.a;
		// 	}
		// }

		// 优化
		// 用Array2D<>模板定义了一个应用于Rgba结构体的数组，该数组只存储10行像素数据。
		// 优化前使用嵌套for循环，时间复杂度O(n^2)，优化后时间复杂度O(n)
		// 优化前是建立一个储存全部像素的数组，现在建立的数组明显占用更小的储存空间，对缓存更友好（空间复杂度）
		// 减少使用了一个int类型的height变量
		// 优化后的代码如下：
		Imf::RgbaInputFile file(path);
		Imath::Box2i dw = file.dataWindow();
		const int width = dw.max.x - dw.min.x + 1;

		// 内存缓冲区，一次进行10行扫描
		Imf::Array2D<Imf::Rgba> pixels(10, width);
		while (dw.min.y <= dw.max.y)
		{
			// 为每个包含10个扫描行的块重复使用缓冲区
			file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width);
			file.readPixels(dw.min.y, glm::min(dw.min.y + 9, dw.max.y)); // processPixels (pixels)
			dw.min.y += 10;
		}
	} catch (Iex::BaseExc &e)
	{
		std::cerr << e.what() << std::endl;
		// glDeleteTextures(1, &textureID);
		throw std::runtime_error("Error loading cube map");
	}
}
